In today’s digital landscape, securing sensitive data is of utmost importance. While HTTPS is a cornerstone of secure communication over the internet, it alone might not fully safeguard sensitive information under certain circumstances. For example, passwords, credit card numbers, or personal identifiers could still be at risk due to potential vulnerabilities such as:
To address these concerns, encrypting sensitive information before transmitting it to the server provides an extra layer of security. Asymmetric encryption algorithms like RSA
are particularly suited for this purpose due to their ability to securely exchange information without requiring a shared secret.
RSA is an asymmetric encryption algorithm that uses a pair of keys: a public key for encryption and a private key for decryption. This makes it ideal for securing sensitive data in client-server communications:
RSA
is widely supported across programming languages and platforms, making it straightforward to integrate into existing systems.
In this article, we will demonstrate how to use RSA
encryption to protect sensitive data during client-server communication.
To illustrate RSA encryption in practice, we will generate a pair of public and private keys on the back-end. The back-end will securely share the public key with the front-end, enabling the front-end to encrypt sensitive data. This encrypted data is then sent to the back-end, where it is decrypted using the private key. By the end of this guide, you will understand how to effectively use RSA to secure communication between your front-end and back-end, providing an additional layer of protection for sensitive information.
Let’s dive into the implementation!
In this simplified implementation, we have a C#
back-end and JavaScript
or TypeScript
front-end, and we follow these steps:
RSA
algorithm and sends the public key to the front-end.
This step-by-step approach ensures that sensitive data remains protected throughout its lifecycle, providing an additional layer of security beyond what HTTPS offers.
In this example, we use BouncyCastle
on our C#
back-end and jsencrypt
on front-end that can be either JavaScript
or TypeScript
.
Let’s proceed by importing the necessary back-end dependencies:
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;
Next, we define a class dedicated to generating new key pairs:
public class RSAKeyPair
{
public string PrivateKey;
public string PublicKey;
private static string ExportAsymmetricKey(AsymmetricKeyParameter key)
{
using (StringWriter writer = new StringWriter())
{
PemWriter pemWriter = new PemWriter(writer);
pemWriter.WriteObject(key);
pemWriter.Writer.Flush();
return writer.ToString();
}
}
public static RSAKeyPair Generate()
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
RSAParameters parameters = rsa.ExportParameters(true);
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(parameters);
// Export private key in the default format (CSP format)
byte[] privateKeyCsp = rsa.ExportCspBlob(includePrivateParameters: true);
string privateKey = Convert.ToBase64String(privateKeyCsp);
// Export private and public keys in PKCS#8 PEM format
string publicKey = ExportAsymmetricKey(keyPair.Public);
return new RSAKeyPair()
{
PrivateKey = privateKey,
PublicKey = publicKey
};
}
}
}
Additionally, we define a class responsible for decrypting data using the private key:
public class RSADecryption
{
public static string Decrypt(string encryptedText, string privateKey)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
// Import the private key from CSP format (Base64-encoded private key)
byte[] privateKeyCsp = Convert.FromBase64String(privateKey);
rsa.ImportCspBlob(privateKeyCsp);
// Decode the Base64-encoded encrypted text
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
// Decrypt the data using OAEP padding
byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, false); // 'false' to turn off OAEP padding, because client-side doesn't support it
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
When the front-end requests a new public key:
cache[random key] = private key with expiration
).
The simplest step is encrypting the data on the front-end using jsencrypt
. Below is the TypeScript
function:
const encrypt = (text: string, publicKey: string) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
const encryptedText = encryptor.encrypt(text);
return encryptedText;
}
That’s it! You’re all set to implement secure encryption and decryption in your system.